﻿using System;
using GammaJul.ReSharper.GammaXaml.Psi.Resolve;
using JetBrains.Annotations;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.ExtensionsAPI.Resolve;
using JetBrains.ReSharper.Psi.ExtensionsAPI.Resolve.Filters;
using JetBrains.ReSharper.Psi.Resolve;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.ReSharper.Psi.Xaml.Tree;
using JetBrains.ReSharper.Psi.Xml.Impl.Util;
using JetBrains.ReSharper.Psi.Xml.Tree;
using JetBrains.Util;

namespace GammaJul.ReSharper.GammaXaml.Psi.Xaml {
	
	internal sealed class CaliburnGuardBoundXamlReference : CaliburnBoundXamlReference {
		private readonly CaliburnBoundXamlReference _ownerReference;
		private readonly PredefinedType _predefinedType;
		
		private bool IsGuardSymbol(ISymbolInfo symbol) {
			IDeclaredElement element = symbol.GetDeclaredElement();
			if (element is IMethod)
				return true;

			var property = element as IProperty;
			if (property == null)
				return false;

			ITypeElement typeElement = property.GetContainingType();
			if (typeElement == null)
				return false;

			return typeElement.IsDescendantOf(_predefinedType.INotifyPropertyChanged.GetTypeElement());
		}

		public override string GetName() {
			return "Can" + base.GetName();
		}

		protected override ISymbolFilter[] GetCompletionFilters() {
			return new ISymbolFilter[] {
				IsPublicFilter.INSTANCE,
				new PredicateFilter(IsGuardSymbol)
			};
		}

		public override ResolveResultWithInfo Resolve(ISymbolTable symbolTable, IAccessContext context) {
			var method = _ownerReference.Resolve().DeclaredElement as IMethod;

			// a guard method/property (eg CanSave) only has a meaning if the owner reference is a method (eg Save),
			// otherwise we just mark the reference as unresolved
			return method == null
				? new ResolveResultWithInfo(EmptyResolveResult.Instance, ResolveErrorType.IGNORABLE)
				: base.Resolve(symbolTable, context);
		}

		protected override IReference BindToInternal(IDeclaredElement declaredElement, ISubstitution substitution) {
			if (!ElementRange.IsValid)
				return this;

			string shortName = declaredElement.ShortName;
			if (shortName.StartsWith("Can", StringComparison.Ordinal))
				shortName = shortName.Substring(3);
			return ReferenceWithTokenUtil.SetText(this, shortName);
		}

		[NotNull]
		internal static CaliburnGuardBoundXamlReference FromDeclarationNameAttribute([NotNull] IDeclarationNameAttribute declName, [NotNull] CaliburnBoundXamlReference ownerReference) {
			IXmlAttributeValue value = declName.Value;
			return new CaliburnGuardBoundXamlReference(declName, value != null ? value.ValueToken : null, ownerReference);
		}

		private CaliburnGuardBoundXamlReference([NotNull] ITreeNode owner, [CanBeNull] IXmlValueToken token, [NotNull] CaliburnBoundXamlReference ownerReference)
			: base(owner, token) {
			_ownerReference = ownerReference;
			_predefinedType = GetPsiModule().GetPredefinedType();
		}
	}

}